﻿using System.Reflection;
using System.Reflection.Emit;

namespace _06_AOP
{
    public class EmitExample
    {
        public class EmitInvocation
        {
            private object? proxy;
            private MethodInfo? method;
            private object[]? arguments;
            public EmitInvocation(object? proxy, MethodInfo? method, object[]? arguments)
            {
                this.proxy = proxy;
                this.method = method;
                this.arguments = arguments;
            }

            public void Proceed()
            {
                method?.Invoke(proxy, arguments);
            }

        }
        public interface IEmitInteceptor
        {
            void Intercept(EmitInvocation invocation);
        }
        public class EmitInteceptor1 : IEmitInteceptor
        {
            public void Intercept(EmitInvocation invocation)
            {
                Console.WriteLine("prox1:start");
                invocation.Proceed();
                Console.WriteLine("prox1:end");
            }
        }
        public class EmitInteceptor2 : IEmitInteceptor
        {
            public void Intercept(EmitInvocation invocation)
            {
                Console.WriteLine("prox2:start");
                invocation.Proceed();
                Console.WriteLine("prox2:end");
            }
        }
        public static class EmitProxyUtil
        {
            public static EmitInvocation GetNextInvocation(Stack<IEmitInteceptor> stack, MethodInfo method, object target, object[] arguments)
            {
                if (stack.Any())
                {
                    var next = stack.Pop();
                    arguments = new object[]
                    { 
                        //递归
                        GetNextInvocation(stack, method, target, arguments)
                    };
                    return new EmitInvocation(next, typeof(IEmitInteceptor).GetMethod(nameof(IEmitInteceptor.Intercept)), arguments);
                }
                else
                {
                    return new EmitInvocation(target, method, arguments);
                }
            }

            public static void Invoke(IEmitInteceptor[] interceptors, MethodInfo method, object target, object[] arguments)
            {
                var stack = new Stack<IEmitInteceptor>(interceptors.Reverse());
                if (stack.Any())
                {
                    var item = stack.Pop();
                    var invocation = GetNextInvocation(stack, method, target, arguments);
                    item.Intercept(invocation);
                }
                else
                {
                    method.Invoke(target, arguments);
                }
            }
        }
        public interface IEmitService
        {
            void Send();
        }
        public class EmitService : IEmitService
        {
            private object _target;
            private IEmitInteceptor[] _inteceptors;

            public EmitService()
            {

            }

            public void Send()
            {
                var method = _target.GetType().GetMethod(nameof(EmitService.Send));
                var arguments = new object[] { };
                EmitProxyUtil.Invoke(_inteceptors, method, _target, new object[] { });
            }
        }
        public static class EmitProxyGenerator
        {
            static AssemblyBuilder _assemblyBuilder;
            static ModuleBuilder _moduleBuilder;
            static EmitProxyGenerator()
            {
                //创建一个程序集
                var assemblyName = new AssemblyName("DynamicProxies");
                _assemblyBuilder = AssemblyBuilder
                    .DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                //创建一个模块
                _moduleBuilder = _assemblyBuilder.DefineDynamicModule("Proxies");
            }
            public static TInterface Create<TInterface>(object target, params IEmitInteceptor[] inteceptor)
                where TInterface : class
            {
                
                #region 定义类型
                //定义一个class，如果这个类型已定义直接返回，缓存
                var typeName = $"{target.GetType().Name}EmitProxy";
                var typeBuilder = _moduleBuilder.DefineType(
                    typeName, 
                    TypeAttributes.Public,typeof(object),
                    new Type[] 
                    { 
                        typeof(TInterface) 
                    });
                #endregion

                #region 定义字段
                //定义字段
                var targetFieldBuilder = typeBuilder.DefineField("target", typeof(object), FieldAttributes.Private);
                var inteceptorFieldBuilder = typeBuilder.DefineField("inteceptor", typeof(IEmitInteceptor[]), FieldAttributes.Private);
                #endregion

                #region 定义构造器
                //定义构造器
                var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.ExplicitThis, new Type[]
                {
                    typeof(object),
                    typeof(IEmitInteceptor[])
                });
                //获取IL编辑器
                var generator = constructorBuilder.GetILGenerator();
                generator.Emit(OpCodes.Ldarg_0);//加载this
                generator.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException());
                generator.Emit(OpCodes.Nop);
                generator.Emit(OpCodes.Nop);
                // this.age = age;
                generator.Emit(OpCodes.Ldarg_0);//加载this
                generator.Emit(OpCodes.Ldarg_1);//加载target参数
                generator.Emit(OpCodes.Stfld, targetFieldBuilder);//加载target字段
                // this.name = name;
                generator.Emit(OpCodes.Ldarg_0);//加载this
                generator.Emit(OpCodes.Ldarg_2);//加载inteceptor参数
                generator.Emit(OpCodes.Stfld, inteceptorFieldBuilder);//加载inteceptor字段
                generator.Emit(OpCodes.Ret);

                #endregion

                #region 实现接口
                var methods = typeof(TInterface).GetMethods();
                foreach (var item in methods)
                {
                    var parameterTypes = item.GetParameters().Select(s => s.ParameterType).ToArray();
                    var methodBuilder = typeBuilder.DefineMethod(item.Name,
                        MethodAttributes.Public| MethodAttributes.Final |MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig,
                        CallingConventions.Standard|CallingConventions.HasThis,
                        item.ReturnType,
                        parameterTypes);
                    var generator1 = methodBuilder.GetILGenerator();
                    //init
                    var methodInfoLocal = generator1.DeclareLocal(typeof(MethodInfo));
                    var argumentLocal = generator1.DeclareLocal(typeof(object[]));
                    generator1.Emit(OpCodes.Nop);
                    generator1.Emit(OpCodes.Ldarg_0);
                    generator1.Emit(OpCodes.Ldfld, targetFieldBuilder);
                    generator1.Emit(OpCodes.Callvirt, typeof(Type).GetMethod(nameof(Type.GetType),Type.EmptyTypes));
                    generator1.Emit(OpCodes.Ldstr, item.Name);
                    generator1.Emit(OpCodes.Callvirt, typeof(Type).GetMethod(nameof(Type.GetMethod), new Type[] { typeof(string) }));
                    generator1.Emit(OpCodes.Stloc, methodInfoLocal);
                    generator1.Emit(OpCodes.Ldc_I4_0);
                    generator1.Emit(OpCodes.Newarr, typeof(object));
                    generator1.Emit(OpCodes.Stloc, argumentLocal);
                    generator1.Emit(OpCodes.Ldarg_0);
                    generator1.Emit(OpCodes.Ldfld, inteceptorFieldBuilder);
                    generator1.Emit(OpCodes.Ldloc_0);
                    generator1.Emit(OpCodes.Ldarg_0);
                    generator1.Emit(OpCodes.Ldfld, targetFieldBuilder);
                    generator1.Emit(OpCodes.Ldc_I4_0);
                    generator1.Emit(OpCodes.Newarr, typeof(object));
                    generator1.Emit(OpCodes.Call, typeof(EmitProxyUtil).GetMethod(nameof(EmitProxyUtil.Invoke)));
                    generator1.Emit(OpCodes.Nop);
                    generator1.Emit(OpCodes.Ret);
                }
                #endregion
                //创建
                var type = typeBuilder.CreateType() ?? throw new ArgumentException();
                var instance = Activator.CreateInstance(type, target, inteceptor);
                return (TInterface)instance;
            }
        }
    }
}
